package jmine.tec.test.utils.spring;

import java.io.Serializable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.HibernateException;
import org.hibernate.validator.InvalidStateException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;

/**
 * Test Case abstrato para auxiliar na realiza��o de testes CRUD. Atenção para os seguintes detalhes semânticos do funcionamento desta
 * classe:
 * <p>
 * - getEntity() deve devolver uma instancia "salvavel"
 * <p>
 * - getInvalidEntity() deve retornar sempre a mesma instância da entidade sendo testada.
 * <p>
 * - modifyEntity() deve modificar a mesma instância retornada por getEntity().
 * <p>
 * - getSavedEntityId() deve retornar o ID da instância retornada por getEntity().
 * 
 * @param <E> O Tipo da Entidade testada
 * @author julien
 */
public abstract class AbstractCrudTestCase<E> extends EnhancedSpringTestCase {

    /**
     * C'tor
     * 
     * @param resource O arquivo de beans a carregar
     */
    public AbstractCrudTestCase(final String resource) {
        super(resource);
    }

    /**
     * C'tor
     * 
     * @param resource O arquivo de beans a carregar
     * @param preferSingleton True para usar o beanFactory singleton
     */
    public AbstractCrudTestCase(final String resource, final boolean preferSingleton) {
        super(resource, preferSingleton);
    }

    /**
     * C'tor
     * 
     * @param beanRefFactory O beanRefFactory que deve ser utilizado
     * @param resource O arquivo de beans a carregar
     * @param preferSingleton True para usar o beanFactory singleton
     */
    public AbstractCrudTestCase(final String beanRefFactory, final String resource, final boolean preferSingleton) {
        super(beanRefFactory, resource, preferSingleton);
    }

    /**
     * Antes de cada teste amarra a sess�o. (bindSession)
     * 
     * @throws Exception ex
     */
    @Override
    protected void setUpBeforeTest() throws Exception {
        super.setUpBeforeTest();
        super.bindSession();
    }

    /**
     * Depois de cada teste solta a sess�o (unbindSession)
     * 
     * @throws Exception ex
     */
    @Override
    protected void tearDownAfterTest() throws Exception {
        super.unbindSession();
        super.tearDownAfterTest();
    }

    /**
     * Retorna a Entidade a ser salva.
     * 
     * @return E
     */
    public abstract E getEntity();

    /**
     * Modifica uma entidade
     * 
     * @param entity a entidade a ser modificada
     */
    public abstract void modifyEntity(E entity);

    /**
     * Retorna uma Entidade inv�lida a ser salva.
     * 
     * @return E
     */
    public abstract E getInvalidEntity();

    /**
     * CREATE Testa a correta cria��o da entidade no BD
     */
    public void testCreate() {
        this.saveEntity(this.getEntity());
    }

    /**
     * CREATE Testa a falha ao criar uma Entidade inv�lida no BD
     */
    public void testInvalidCreate() {
        this.saveInvalidEntity();
    }

    /**
     * READ Testa a correta recuperacao de uma Entidade no BD
     */
    public void testRead() {
        E entity = this.getEntity();
        this.saveEntity(entity);
        Serializable id = this.getSession().getIdentifier(entity);
        E e = this.getSavedEntity(id);
        assertNotNull(e);
    }

    /**
     * UPDATE Testa a correta atualizacao de uma Entidade no BD
     */
    public void testUpdate() {
        E e = this.getEntity();
        this.saveEntity(e);
        Serializable id = this.getSession().getIdentifier(e);

        e = this.getSavedEntity(id);
        assertNotNull(e);

        this.modifyEntity(e);
        this.saveEntity(e);

        E e2 = this.getSavedEntity(id);
        assertNotNull(e2);
        // assertEquals(
        // "equals não deve contar com a igualdade de objetos: implemente o
        // metodo equals",
        // getEntity(), e2);
    }

    /**
     * DELETE Testa a correta remocao de uma Entidade no BD
     */
    public void testDelete() {
        E entity = this.getEntity();
        this.saveEntity(entity);
        Serializable id = this.getSession().getIdentifier(entity);

        E e = this.getSavedEntity(id);
        assertNotNull(e);

        this.deleteEntity(e);

        this.getSession().flush();

        E e2 = this.getSavedEntity(id);
        assertNull(e2);
    }

    /**
     * Deleta a Entidade.
     * 
     * @param entity the entity to delete
     */
    private void deleteEntity(final Object entity) {
        this.getSession().delete(entity);
        this.getSession().flush();
    }

    /**
     * Tenta salvar uma Entidade no BD e dá um flush na Session. Falha ser lançar alguma Exception.
     * 
     * @param entity a entidade a ser salva
     */
    private void saveEntity(final E entity) {
        this.getSession().saveOrUpdate(entity);
        this.getSession().flush();
    }

    /**
     * tenta salvar uma Entidade inv�lida no BD. Falha se conseguir.
     */
    private void saveInvalidEntity() {
        Log logger = LogFactory.getLog(this.getClass());
        try {
            E invalidEntity = this.getInvalidEntity();
            if (invalidEntity == null) {
                logger.warn("null entity returned on saveInvalidEntity");
            } else {
                this.getSession().saveOrUpdate(invalidEntity);
                this.getSession().flush();
                fail("Nao deveria inserir mas inseriu!");
            }
        } catch (InvalidStateException e) {
            logger.info("exception was thrown: ", e);
        } catch (HibernateException ex) {
            logger.info("exception was thrown: ", ex);
        } catch (DataIntegrityViolationException e) {
            logger.info("exception was thrown: ", e);
        } catch (DataAccessException e) {
            logger.info("exception was thrown: ", e);
        }
    }

    /**
     * Retorna uma entidada ja salva ou null.
     * 
     * @param idEntity o id da entidade
     * @return E
     */
    @SuppressWarnings("unchecked")
    protected final E getSavedEntity(final Serializable idEntity) {
        this.getSession().flush();
        this.getSession().clear();
        return (E) this.getSession().get(this.getEntity().getClass(), idEntity);
    }
}
